package com.segersten.othello.service.akka;

import java.util.concurrent.ConcurrentHashMap;

import com.segersten.othello.service.akka.game.BoardActorFactory;
import com.segersten.othello.service.akka.game.DefaultBoardActorFactory;
import com.segersten.othello.service.akka.game.OthelloBoardServiceActor;
import com.segersten.othello.service.akka.game.dao.InMemoryGameDao;
import com.segersten.othello.service.akka.lobby.LobbyServiceActor;
import com.segersten.othello.service.akka.lobby.DefaultLobbyServiceActorFactory;
import com.segersten.othello.service.akka.lobby.LobbyServiceActorFactory;
import com.segersten.othello.service.akka.lobby.dao.InMemoryLoginRegistry;
import com.segersten.othello.service.akka.lobby.dao.InMemoryOngoingGamesDao;
import com.segersten.othello.service.akka.lobby.dao.InMemoryPlayerScoreDao;
import com.segersten.othello.service.akka.lobby.dao.InMemoryRegisteredForMatchingDao;

public class InMemorySingleInstanceCache implements GameObjectPool {
    
    private static ConcurrentHashMap<GameObjectReference, Actor> objectPool 
            =  new ConcurrentHashMap<GameObjectReference, Actor>();
    
    private static ConcurrentHashMap<GameObjectReference, InMemoryGameDao> gameDaoPool 
    =  new ConcurrentHashMap<GameObjectReference, InMemoryGameDao>();
	
    private static GameObjectReference lobbyRef = null;

    private final LobbyServiceActorFactory lobbyFactory;

    private final DefaultBoardActorFactory boardActorFactory;
     
    // For test, nnneeeeejjjjj
	public InMemorySingleInstanceCache(LobbyServiceActorFactory factory, DefaultBoardActorFactory boardActorFactory)
    {   
	    this.lobbyFactory = factory;
	    this.boardActorFactory = boardActorFactory;
    }
	
	// For boot
	public InMemorySingleInstanceCache()
    {   
        this(null, null);
    }

    @Override
    public GameObjectReference actorFor(String reference) {
	    assertNotNull(reference);
	    ensureLobby(reference);
	    GameObjectReference ref = new GameObjectReference(reference);
        return objectPool.containsKey(ref) ? ref : null;
	}

	private void ensureLobby(String reference)
    {
        if (lobbyRef == null && LobbyServiceActor.SERVICE_NAME.equals(reference)) {
            synchronized (LobbyServiceActor.SERVICE_NAME) {
                BoardActorFactory baf = (boardActorFactory==null) ? 
                        new DefaultBoardActorFactory(this) : 
                        boardActorFactory;;
                LobbyServiceActorFactory lsaf = (lobbyFactory==null) ? 
                        new DefaultLobbyServiceActorFactory(this, 
                                    baf, 
                                    new InMemoryRegisteredForMatchingDao(),
                                    new InMemoryPlayerScoreDao(),
                                    new InMemoryLoginRegistry(),
                                    new InMemoryOngoingGamesDao()):
                        lobbyFactory;
                objectPool.putIfAbsent(new GameObjectReference(LobbyServiceActor.SERVICE_NAME), lsaf.create());
            }
        }
    }

    private void assertNotNull(Object obj)
    {
        if (obj==null) throw new IllegalArgumentException("Cannot be null");
    }

    @Override
    public GameObjectReference createBoardServiceActor(String reference) {
        assertNotNull(reference);
        OthelloBoardServiceActor bsa = new OthelloBoardServiceActor(reference);
        gameDaoPool.put(new GameObjectReference(reference), new InMemoryGameDao());
        GameObjectReference ref = new GameObjectReference(reference);
	    objectPool.putIfAbsent(ref, bsa);
	    return ref;
	}

	@Override
    public String getReference(GameObjectReference reference) {
	    assertNotNull(reference);
	    return (String) reference.getId();
    }
    
    @Override
    public void tell(GameObjectReference reference, Object message)
    {
        assertNotNull(reference);
        assertNotNull(message);
        try {
            objectPool.get(reference).onReceive(this, gameDaoPool.get(reference), message);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
    
	@Override
    public Object ask(GameObjectReference reference, Object message) {
	    assertNotNull(reference);
	    assertNotNull(message);
	    try {
            return objectPool.get(reference).onReceive(this, gameDaoPool.get(reference), message);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
	}

    public void clearAll()
    {
        objectPool.clear();
        lobbyRef = null;
    }
    
}
